package de.dailab.graphworld.bean;

import de.dailab.graphworld.bean.memory.CurrPositionInfo;
import de.dailab.graphworld.bean.memory.HomeInfo;
import de.dailab.graphworld.ontology.AgentType;
import de.dailab.graphworld.ontology.RequestProtocol;
import de.dailab.graphworld.ontology.Edge;
import de.dailab.graphworld.ontology.Task;
import de.dailab.graphworld.ontology.RegistrationRequest;
import de.dailab.graphworld.ontology.Registration;
import de.dailab.graphworld.ontology.Node;
import de.dailab.graphworld.ontology.PositioningMessage;
import de.dailab.graphworld.ontology.Reply;
import de.dailab.graphworld.pathfinding.AStar;
import de.dailab.graphworld.world.GraphWorldAgentBean;
import de.dailab.jiactng.agentcore.action.Action;
import de.dailab.jiactng.agentcore.action.ActionResult;
import de.dailab.jiactng.agentcore.comm.IGroupAddress;
import de.dailab.jiactng.agentcore.comm.message.JiacMessage;
import de.dailab.jiactng.agentcore.knowledge.IFact;
import de.dailab.jiactng.agentcore.ontology.IActionDescription;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;

/**
 *
 * @author Kseniya
 */
public class BeanCarrier extends BeanPhysical {

    public static final String GROUP_NAME = "carriers";
    private IActionDescription actionTakeGold;
    private IActionDescription actionDropGold;
    private IActionDescription actionMove;
    private IActionDescription actionExplore;
    private final Queue<ActionContainer> taskActions = new LinkedList<>();
    private final Queue<Node> moves = new LinkedList<>();
    private Task currentTask;

    @Override
    public void doStart() {
        super.doStart();
        actionMove = thisAgent.searchAction(new Action(GraphWorldAgentBean.ACTION_MOVE));
        actionExplore = thisAgent.searchAction(new Action(GraphWorldAgentBean.ACTION_EXPLORE));
        actionTakeGold = thisAgent.searchAction(new Action(GraphWorldAgentBean.ACTION_TAKE_GOLD));
        actionDropGold = thisAgent.searchAction(new Action(GraphWorldAgentBean.ACTION_DROP_GOLD));
    }

    @Override
    public void execute() {
        super.execute();
        if (currentTask != null && taskActions.isEmpty()) {
            moves.clear();
            planActions(currentTask);
        }
        if (!taskActions.isEmpty()) {
            moves.clear();
            ActionContainer nextAction = taskActions.peek();
            invoke(nextAction.action, nextAction.params, this);
        } else if (!moves.isEmpty()) {
            invoke(actionMove, new Serializable[]{moves.element()}, this);
        }
    }

    @Override
    public void receiveResult(ActionResult result) {
        String actionName = result.getAction().getName();
        if (result.getFailure() != null) {
//            log.error(actionName);
//            log.error(result.getFailure());
//            log.error("curr node: " + memory.read(new CurrPositionInfo(null)).getPos().getID());
            
            // no gold was found, inform broker
            if (GraphWorldAgentBean.ACTION_TAKE_GOLD.equals(actionName)) {
                sendMsgGroup(new Reply(currentTask, RequestProtocol.FAILURE), AgentType.CARRIER.getAddress());
                taskActions.clear();
                currentTask = null;
                explore();
            }
        } else {
            switch (actionName) {
                case GraphWorldAgentBean.ACTION_INIT_TEAM_POSITION:
                    // store home node
                    Node curr = (Node) result.getResults()[0];
                    memory.write(new CurrPositionInfo(curr));
                    memory.write(new HomeInfo(curr));
                    sendMsgGroup(new PositioningMessage(curr), AgentType.CARRIER.getAddress());
                    explore();
                    break;
                case GraphWorldAgentBean.ACTION_EXPLORE:
                    // queue random move
                    Set<Node> neighbours = new HashSet<>((Set<Node>) result.getResults()[0]);
                    moves.offer(selectMoveTarget(neighbours));
                    break;
                case GraphWorldAgentBean.ACTION_MOVE:
                    curr = (Node) result.getResults()[0];
                    memory.update(new CurrPositionInfo(null), new CurrPositionInfo(curr));
                    sendMsgGroup(new PositioningMessage(curr), AgentType.CARRIER.getAddress());
                    if (taskActions.isEmpty()) {
                        // carrier moved freely
                        moves.poll();
                        explore();
                        super.receiveResult(result);
                        return;
                    }
                    // carrier moved while executing task
                    taskActions.poll();
                    if (taskActions.isEmpty()) {
                        currentTask = null;
                    } else {
                        ActionContainer nextAction = taskActions.element();
                        if (nextAction.action == actionTakeGold) {
                            // update gold node in task
                            currentTask.goldNode = curr;
                            invoke(nextAction.action, null, this);
                        } else if (nextAction.action == actionDropGold) {
                            invoke(nextAction.action, null, this);
                        }
                    }
                    // move action will be executed automatically by the next execute call
                    break;
                case GraphWorldAgentBean.ACTION_TAKE_GOLD:
                    taskActions.poll();
                    break;
                case GraphWorldAgentBean.ACTION_DROP_GOLD:
                    // inform broker about task success and forget about it
                    sendMsgGroup(new Reply(currentTask, RequestProtocol.INFORM_RESULT), AgentType.CARRIER.getAddress());
                    currentTask = null;
                    taskActions.clear();
                    break;
                default:
                    break;
            }
        }
        super.receiveResult(result);
    }

    @Override
    protected boolean isInitialisedInWorld() {
        HomeInfo home = memory.read(new HomeInfo(null));
        return home != null;
    }

    @Override
    protected Collection<IGroupAddress> getGroups() {
        return Collections.singleton(AgentType.CARRIER.getAddress());
    }

    private void planActions(Task msg) {
        Node target = msg.goldNode;
        Node currPos = memory.read(new CurrPositionInfo(null)).getPos();
        Node home = memory.read(new HomeInfo(null)).getHome();

        List<Edge> path;

        if (currPos.getID() == target.getID()) {
            path = Collections.emptyList();
        } else {
            path = AStar.findPath(currPos, target);
        }
        for (Edge edge : path) {
            taskActions.add(new ActionContainer(actionMove, new Serializable[]{edge.getTarget()}));
        }
        taskActions.add(new ActionContainer(actionTakeGold, null));
        if (target.getID() == home.getID()) {
            path = Collections.emptyList();
        } else {
            path = AStar.findPath(target, home);
        }
        for (Edge edge : path) {
            taskActions.add(new ActionContainer(actionMove, new Serializable[]{edge.getTarget()}));
        }
        taskActions.add(new ActionContainer(actionDropGold, null));

        System.out.println("planned actions from "
                + currPos.getID() + " to " + target.getID()
                + "and back home to " + home.getID() + ": " + taskActions.toString());
    }

    private void explore() {
        this.invoke(actionExplore, null, this);
    }

    private Node selectMoveTarget(Collection<Node> nodes) {
        Node current = memory.read(new CurrPositionInfo(null)).getPos();
        Iterator<Node> it = nodes.iterator();
        while (it.hasNext()) {
            Node node = it.next();
            if (node.getID() == current.getID()) {
                it.remove();
            }
        }
        return nodes.iterator().next();
    }

    @Override
    public void consume(JiacMessage msg) {
        IFact payload = msg.getPayload();
        // registration request from broker, answer him
        if (payload instanceof RegistrationRequest) {
            sendMsgPersonal(new Registration(thisAgent.getAgentDescription(), AgentType.CARRIER), msg.getSender());
        } else if (payload instanceof Task) {
            Task task = (Task) payload;
            if (!taskActions.isEmpty()) {
                // this carrier is already busy
                sendMsgPersonal(new Reply(task, RequestProtocol.REFUSE), msg.getSender());
            } else {
                // if this carrier is not busy, inform about participation and start
                currentTask = task;
                sendMsgPersonal(new Reply(currentTask, RequestProtocol.AGREE), msg.getSender());
//                planActions(currentTask);
                // no more free-style moves
                moves.clear();
            }
        }
    }

    private class ActionContainer {

        private final IActionDescription action;
        private final Serializable[] params;

        public ActionContainer(IActionDescription action, Serializable[] params) {
            this.action = action;
            this.params = params;
        }

        @Override
        public String toString() {
            String params2Str = "";
            if (params != null && params.length > 0 && params[0] != null) {
                params2Str += ((Node) params[0]).getID();
            }
            return action.getName() + params2Str;
        }
    }
}
